package com.wg.net.client.core;

import cn.hutool.log.Log;
import cn.hutool.log.LogFactory;
import com.wg.core.io.BigEndianBinaryReader;
import com.wg.net.client.WgClient;
import com.wg.net.client.tcp.operation.InitRequest;
import com.wg.net.client.tcp.operation.InitResponse;
import com.wg.net.operations.OperationCode;
import com.wg.net.operations.PingRequest;
import com.wg.net.operations.base.OperationRequest;
import com.wg.net.operations.base.OperationResponse;
import com.wg.net.protocols.BinaryByteProtocol;
import com.wg.net.protocols.IProtocol;
import com.wg.net.protocols.MessageHeader;
import com.wg.net.protocols.MessageType;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.epoll.Epoll;
import io.netty.channel.epoll.EpollIoHandler;
import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.util.concurrent.Future;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 基础网络客户端抽象类（实现通用连接管理、心跳检测、协议处理）
 * <p>
 * 职责：
 * - 封装 Netty 通道生命周期管理
 * - 实现心跳检测与超时断开机制
 * - 提供协议序列化/反序列化入口
 * - 管理连接状态与资源释放
 *
 * @author 少爷123
 */
public abstract class AbstractClient implements IClient {
    private static final Log log = LogFactory.get();
    protected final IProtocol protocol = BinaryByteProtocol.getInstance();
    protected final WgClient wgClient;
    protected final ClientConfig config;
    protected String connectionId;
    protected ConnectionState connectionState;
    protected long lastPingResult;
    protected long timestampOfLastReceive;
    protected boolean isSsl;
    protected boolean connected;
    protected final ReentrantLock stateLock = new ReentrantLock();

    protected MultiThreadIoEventLoopGroup workerGroup;
    protected Channel channel;

    public AbstractClient(WgClient wgClient, ClientConfig config) {
        this.wgClient = wgClient;
        this.config = config;
        final IoHandlerFactory ioFactory = Epoll.isAvailable() ? EpollIoHandler.newFactory()
                : NioIoHandler.newFactory();
        this.workerGroup = new MultiThreadIoEventLoopGroup(1, ioFactory);
    }

    public String getConnectionId() {
        return connectionId;
    }

    public int getPingInterval() {
        return this.wgClient.getPingInterval();
    }

    public int getTimeout() {
        return this.wgClient.getTimeout();
    }

    @Override
    public boolean isConnected() {
        return connected;
    }

    public void setConnected(boolean connected) {
        this.connected = connected;
    }

    public WgClient getWgClient() {
        return wgClient;
    }

    @Override
    public void connect() {
        if (channel != null && channel.isActive()) {
            log.warn("Tcp client is allready connected");
            return;
        }
            if (connectionState != ConnectionState.Disconnected) {
                log.warn("当前状态不允许连接 | 状态={}", connectionState);
                return;
            }

        stateLock.lock();
        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(workerGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, this.getTimeout())
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(createInitializer());

            connectionState = ConnectionState.Connecting;
            bootstrap.connect(config.getIp(), config.getPort()).addListener(future -> {
                handleConnectionResult(future);
            });
        } finally {
            stateLock.unlock();
        }
    }

    private void handleConnectionResult(Future<? super Void> future) {
        stateLock.lock();
        try {
            if (future.isSuccess()) {
                channel = ((ChannelFuture) future).channel();
                this.connected = true;
                this.wgClient.onConnected();
                sendInitRequest(config.getAppId());
            } else {
                this.connected = false;
                this.wgClient.onConnectionFail();
            }
        } finally {
            stateLock.unlock();
        }
    }

    @Override
    public void close() {
        stateLock.lock();
        try {
            doClose();
        } finally {
            stateLock.unlock();
        }
    }

    /**
     * 执行实际的关闭逻辑（子类可重写）
     */
    private void doClose() {
        this.connectionState = ConnectionState.Disconnecting;
        if (channel != null && channel.isActive()) {
            channel.close().addListener(future -> {
                if (future.isSuccess()) {
                    log.debug("Channel closed successfully");
                } else {
                    log.error("Failed to close channel: {}", future.cause().getMessage());
                }
            });
            channel = null;
        }
        connectionState = ConnectionState.Disconnected;
        connected = false;
    }

    @Override
    public void dispose() {
        close();
        workerGroup.shutdownGracefully(0, 5, TimeUnit.SECONDS);
    }

    @Override
    public void sendOperationRequest(OperationRequest request) {
        byte[] data = protocol.serializeOperationRequest(request, MessageType.Operation);
        this.sendData(data);
    }

    private void incomingCommands() {
        if (connectionState != ConnectionState.Connected) {
            return;
        }

        long nowMills = System.currentTimeMillis();

        if (this.wgClient.getDisconnectTimeout() > 0
                && nowMills - timestampOfLastReceive > this.wgClient.getDisconnectTimeout()) {
            close();
        }

        if (this.wgClient.getPingInterval() > 0
                && Math.abs(nowMills - lastPingResult) > this.wgClient.getPingInterval()) {
            sendPing();
        }
    }

    public void onReceive(byte[] data) {
        MessageHeader header = protocol.parseMessageHeader(data);
        if (header == null) {
            return;
        }

        timestampOfLastReceive = System.currentTimeMillis();
        this.onOperationResponseInternal(header, data);
    }

    protected void sendInitRequest(String appId) {
        InitRequest initRequest = new InitRequest();
        initRequest.setAppId(appId);
        OperationRequest request = new OperationRequest(OperationCode.INIT_REQUEST, initRequest);
        byte[] buff = protocol.serializeInitRequest(request);
        this.sendData(buff);
    }

    protected boolean initCallback(byte[] data) {
        OperationResponse response = protocol.parseOperationResponse(data, false);
        if (response == null) {
            log.debug("Failed to parse initResponse for peer with connection id {}.", this.connectionId);
            return false;
        }

        InitResponse initResponse = new InitResponse();
        initResponse.readBytes(new BigEndianBinaryReader(response.getBuffer()));

        this.connectionId = initResponse.getConnectionId();
        this.lastPingResult = System.currentTimeMillis();
        this.connectionState = ConnectionState.Connected;

        this.workerGroup.scheduleWithFixedDelay(this::incomingCommands, 1, 1, TimeUnit.SECONDS);
        return true;
    }

    private void onOperationResponseInternal(MessageHeader header, byte[] data) {
        OperationResponse response = protocol.parseOperationResponse(data, header.isZip());
        if (response == null) {
            log.debug("Failed to parse internal operation response for peer with connection id {}.", this.connectionId);
        } else {
            if (response.getOpCode() == 0) {
                initCallback(data);
            } else {
                this.wgClient.onOperationResponse(response);
            }
        }
    }

    protected void sendPing() {
        lastPingResult = System.currentTimeMillis();
        PingRequest pingRequest = new PingRequest(lastPingResult);

        OperationRequest operationRequest = new OperationRequest(OperationCode.PING, pingRequest);
        byte[] buff = protocol.serializeOperationRequest(operationRequest, MessageType.Operation);
        this.sendData(buff);
    }

    public void sendData(byte[] data) {
        if (channel == null || !channel.isActive()) {
            return;
        }
        ChannelFuture future = doSend(data);
        if (future != null) {
            future.addListener(f -> {
                if (f.isSuccess()) {
                    log.debug("send data success");
                } else {
                    log.error("send data fail", f.cause());
                }
            });
        }
    }

    /**
     * 创建初始化器
     * 
     * @return 初始化器
     */
    protected abstract ChannelInitializer<? extends Channel> createInitializer();

    /**
     * 发送数据
     * 
     * @param data
     * @return 发送结果
     */
    protected abstract ChannelFuture doSend(byte[] data);
}
